﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using TibcoQueueMonitor.Entities;
using TibcoQueueMonitor.TibcoAdminManagers;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Timers;
using System.ComponentModel;
using TibcoQueueMonitor.Controls;
using TibcoQueueMonitor.Interfaces;

namespace TibcoQueueMonitor
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Constants
        private const string TabNameAvailableQueues = "Available Queues";
        private const string TabNameConnectedQueues = "Current Connections";
        private const string BrushIllegalWarning = "IllegalWarningsBrush";
        private const string BrushWarning = "WarningBrush";
        private const string BrushProfileBar = "ProfileBarBrush";

        #endregion //Constants

        #region Private Fields
        private object _refreshLockToken = new object();
        private bool _offlineDebugAdminData = false;
        private bool _refreshBusy;
        private Dictionary<string, LinearGradientBrush> _brushes;
        private Timer _inputSearchTriggerTimer;
        private Timer _autoRefreshTimer;
        /// <summary>
        /// Delegate that is called when a search thread needs to interact with
        /// the UI thread.
        /// </summary>
        private delegate void SearchHandler();
        /// <summary>
        /// Delegate this called when the refresh auto-elapse thread needs to
        /// interact with the UI thread.
        /// </summary>
        private delegate void RefreshHandler();

        /// <summary>
        /// The offline data generator object that provides offline
        /// data when its Generate method is called.
        /// </summary>
        private IOfflineDataGenerator _offlineDataGenerator;

        /// <summary>
        /// The working set of the administration data object model. Modifying
        /// this does not automatically update the grids. A call to the
        /// ShowAdminData() method is required.
        /// </summary>
        private TibcoAdministrationData _data;

        #endregion //Private Fields

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // Fill window title with assembly version number
            this.Title = String.Concat(this.Title, " ", Assembly.GetExecutingAssembly().GetName().Version.ToString());

            // Initialize brushes used for display
            BuildBrushes();

            // Initialize private fields
            _data = new TibcoAdministrationData();
            _refreshBusy = false;
            _inputSearchTriggerTimer = new Timer(500);
            _inputSearchTriggerTimer.AutoReset = false;
            _inputSearchTriggerTimer.Elapsed += new ElapsedEventHandler(_inputSearchTriggerTimer_Elapsed);
            _autoRefreshTimer = new Timer(TibcoAdminConfigurationManager.GetAutoRefreshInterval() * 1000);
            _autoRefreshTimer.AutoReset = true;
            _autoRefreshTimer.Elapsed += new ElapsedEventHandler(_autoRefreshTimer_Elapsed);
        }

        #endregion //Constructors

        #region Private Methods
            #region Private Methods.Events
                #region Private Methods.Events.Profile
        private void profileCtrl_ProfilesRefreshed(object sender, EventArgs e)
        {
            // Upon a refresh of profiles, we want to ensure the expander bar
            // displays the correct information.
            UpdateExpanderHeader();
        }

        private void profileCtrl_ProfileSelectionChanged(object sender, EventArgs e)
        {
            // When the profile changes, we want to ensure that the dashboard
            // is updated with the correct information, then kick off a refresh
            // of the data for convenience.
            UpdateDashboard();

            RefreshData();
        }

        private void profileCtrl_StatusNotification(object sender, StatusNotificationEventArgs e)
        {
            // Update the status bar with any notifications that emerge from the
            // profile control.
            UpdateStatus(e.Message, e.IsError);
        }

        private void profileCtrl_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
        {
            // If the keyboard focus leaves the profile control, shrink the
            // expander control to reveal the dashboard controls (which
            // includes the 'Refresh' button.
            if (!profileCtrl.IsKeyboardFocusWithin && !expProfiles.IsKeyboardFocusWithin)
            {
                expProfiles.IsExpanded = false;
            }
        }

        private void profileCtrl_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
        {
            // If the keyboard focus enters the profile control, expand the
            // expander control to reveal the profile controls
            if (profileCtrl.IsKeyboardFocusWithin)
            {
                expProfiles.IsExpanded = true;
            }
        }

                #endregion //Private Methods.Events.Profile

                #region Private Methods.Events.Controls
        private void btnRefresh_Click(object sender, RoutedEventArgs e)
        {
            RefreshData();
        }

        private void txtbAvailableQueueSearch_TextChanged(object sender, TextChangedEventArgs e)
        {
            _inputSearchTriggerTimer.Enabled = false;
            _inputSearchTriggerTimer.Enabled = true;
            _inputSearchTriggerTimer.Start();
        }

        private void txtbCurrentConnectionsSearch_TextChanged(object sender, TextChangedEventArgs e)
        {
            _inputSearchTriggerTimer.Enabled = false;
            _inputSearchTriggerTimer.Enabled = true;
            _inputSearchTriggerTimer.Start();
        }

        private void dgAvailableQueues_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (dgAvailableQueues.SelectedItem is TibcoQueue)
            {
                txtbCurrentConnectionsSearch.Text = (dgAvailableQueues.SelectedItem as TibcoQueue).QueueName;

                foreach (object item in tabCtrlQueues.Items)
                {
                    if (item is TabItem && ((item as TabItem).Header.ToString() == "Current Connections"))
                    {
                        tabCtrlQueues.SelectedIndex = 1;
                        e.Handled = true;
                    }
                }

            }
        }

        private void dgAvailableQueues_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            if (e.Row.DataContext is TibcoQueue)
            {
                TibcoQueue queueRow = e.Row.DataContext as TibcoQueue;
                if (queueRow.Receivers == 2)
                {
                    e.Row.Background = _brushes[BrushWarning];
                }
                else if (queueRow.Receivers > 2)
                {
                    e.Row.Background = _brushes[BrushIllegalWarning];
                }
                else
                {
                    e.Row.Background = null;
                }
            }
        }

        private void expProfiles_ExpandOrCollapse(object sender, RoutedEventArgs e)
        {
            UpdateExpanderHeader();
        }

        private void chkbAutoRefresh_Checked(object sender, RoutedEventArgs e)
        {
            _autoRefreshTimer.Start();
        }

        private void chkbAutoRefresh_Unchecked(object sender, RoutedEventArgs e)
        {
            _autoRefreshTimer.Stop();
        }

                #endregion //Private Methods.Events.Controls

                #region Private Methods.Events.Threading
        private void manager_AdminDataRetrieved(object sender, AdminDataEventArgs e)
        {
            _refreshBusy = false;

            if (e.Data != null)
            {
                UpdateStatus("Data retrieval completed");

                if (_data != null)
                {
                    lock (_data.LockObject)
                    {
                        _data = e.Data;
                    }
                }
                else
                {
                    _data = e.Data;
                }

                if (_data.IsTestData)
                {
                    UpdateStatus("Data retrieval completed (results consist of offline test data)", true);
                }

                lblLastRefresh.Content = String.Concat("Last Refresh: ", DateTime.Now.ToString());
            }
            else
            {
                _data.Connections.Clear();
                _data.Consumers.Clear();
                _data.Queues.Clear();

                if (e != null & e.Exception != null)
                {
                    UpdateStatus(e.Exception.Message, true);
                }
                else
                {
                    UpdateStatus("An unknown error was found while trying to retrieve data.", true);
                }
            }

            btnRefresh.IsEnabled = true;

            ShowAdminData();
        }

        private void _inputSearchTriggerTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _inputSearchTriggerTimer.Stop();
            _inputSearchTriggerTimer.Enabled = false;
            SearchHandler searchMethod = new SearchHandler(this.Search);

            tabCtrlQueues.Dispatcher.Invoke(searchMethod, null);
        }

        private void _autoRefreshTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (!_refreshBusy)
            {
                RefreshHandler refreshMethod = new RefreshHandler(this.RefreshData);
                btnRefresh.Dispatcher.Invoke(refreshMethod, null);
            }
        }

                #endregion //Private Methods.Events.Threading

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Upon startup, obtain all available profiles. After the profiles
            // are loaded, update the dashboard with the currently selected
            // profile, if applicable.
            try
            {
                _offlineDebugAdminData = TibcoAdminConfigurationManager.GetDebugModeFlag();

                _offlineDataGenerator = CreateOfflineData();

                // Get profiles from EMS Server file
                profileCtrl.RefreshProfiles();

                UpdateDashboard();
            }
            catch (Exception ex)
            {
                UpdateStatus(ex.Message, true);
            }
        }

            #endregion //Private Methods.Events

        /// <summary>
        /// Refreshes the data by performing checks,then making a call to the
        /// TibcoAdminManager to obtain the latest data from the EMS server.
        /// </summary>
        private void RefreshData()
        {
            btnRefresh.IsEnabled = false;
            string error = null;

            error = CheckSelectedProfile();
            if (!string.IsNullOrWhiteSpace(error))
            {
                UpdateStatus(error);

                return;
            }

            EmsServerProfile profile = profileCtrl.SelectedItem as EmsServerProfile;
            TibcoAdminManager manager = new TibcoAdminManager(profile.Hostname, profile.Port, profile.User, profile.Password, _offlineDataGenerator);
            manager.ReturnTestData = _offlineDebugAdminData;

            try
            {
                lock (_refreshLockToken)
                {
                    _refreshBusy = true;

                    manager.AdminDataRetrieved += new EventHandler<AdminDataEventArgs>(manager_AdminDataRetrieved);
                    manager.StartRetrieveAdministrationData();
                }
            }
            catch (Exception ex)
            {
                UpdateStatus(ex.Message, true);
            }

            UpdateStatus("Retrieving Data...");
        }

        /// <summary>
        /// Writes a message to the status bar.
        /// </summary>
        /// <param name="statusText">The status text.</param>
        private void UpdateStatus(string statusText)
        {
            UpdateStatus(statusText, false);
        }

        /// <summary>
        /// Writes a message to the status bar
        /// </summary>
        /// <param name="statusText">The status text.</param>
        /// <param name="warning">if set to <c>true</c>, indicates that the
        /// message is a warning that requires user attention.</param>
        /// <remarks>
        /// Setting the warning flag to true will cause the status bar to
        /// change to a color that attracts the user's attention.
        /// </remarks>
        private void UpdateStatus(string statusText, bool warning)
        {
            lblStatusLabel.Content = String.Format("[{0}] {1}", DateTime.Now.ToLongTimeString(), statusText);

            if (warning)
            {
                statusBar.Background = _brushes[BrushWarning];
            }
            else
            {
                statusBar.Background = null;
            }
        }

        /// <summary>
        /// Updates the dashboard with the current profile settings.
        /// </summary>
        /// <remarks>
        /// The dashboard is the set of fields that appear when the profile
        /// drop down is collapsed (i.e. not showing free-text fields that
        /// allow you to modify the currently selected profile).
        /// </remarks>
        private void UpdateDashboard()
        {
            // Perform a check against the current profile selection. If a
            // valid profile is selected, update the dashboard, otherwise
            // clear it.

            string error = CheckSelectedProfile();
            if (error == null)
            {
                // Update dashboard with current profile
                EmsServerProfile profile = profileCtrl.SelectedItem as EmsServerProfile;
                lblEmsServerValue.Content = profile.Hostname;
                lblPortValue.Content = profile.Port;
                lblUsernameValue.Content = profile.User;              
            }
            else
            {
                // Clear the dashboard
                lblEmsServerValue.Content = null;
                lblUsernameValue.Content = null;
                lblPortValue.Content = null;
            }
        }

        /// <summary>
        /// Utility method that checks the selected profile.
        /// </summary>
        /// <returns>
        /// <c>null</c> if a valid profile was found, otherwise a
        /// message indicating the problem.
        /// </returns>
        private string CheckSelectedProfile()
        {
            if (!(profileCtrl.SelectedItem is EmsServerProfile))
            {
                if (profileCtrl.SelectedItem == null)
                {
                    return "No profile is selected.";
                }
                else
                {
                    return "Serious error encountered in the application. Unable to evaluate profile.";
                }
            }

            return null;
        }

        /// <summary>
        /// Presents the data in the current working set to the grids.
        /// </summary>
        /// <remarks>
        /// This method needs to be called everytime the data set has
        /// changed.
        /// </remarks>
        private void ShowAdminData()
        {
            //// Update Available Queues
            ApplyAvailableQueuesGridFilter();

            //// Update Connected Queues
            ApplyConnectionsGridFilter();
        }

        private void Search()
        {
            TabItem currentTab = (tabCtrlQueues.SelectedItem as TabItem);
            switch (currentTab.Header.ToString())
            {
                case TabNameAvailableQueues:
                    ApplyAvailableQueuesGridFilter();
                    break;
                case TabNameConnectedQueues:
                    ApplyConnectionsGridFilter();
                    break;
            }
        }

        private void ApplyAvailableQueuesGridFilter()
        {
            try
            {
                dgAvailableQueues.ItemsSource = from filteredQueue in _data.Queues.Values
                                                where Regex.IsMatch(filteredQueue.QueueName, txtbAvailableQueueSearch.Text, RegexOptions.IgnoreCase)
                                                select filteredQueue;
            }
            catch (ArgumentException)
            {
                UpdateStatus("Invalid search criteria specified.", true);
            }
            catch (Exception ex)
            {
                UpdateStatus(ex.Message, true);
            }
        }

        private void ApplyConnectionsGridFilter()
        {
            try
            {
                dgCurrentConnections.ItemsSource = from filteredConnection in GenerateConnectionDisplayEntities()
                                                   where (Regex.IsMatch(filteredConnection.Host, txtbCurrentConnectionsSearch.Text, RegexOptions.IgnoreCase) ||
                                                    Regex.IsMatch(filteredConnection.QueueName, txtbCurrentConnectionsSearch.Text, RegexOptions.IgnoreCase) ||
                                                    Regex.IsMatch(filteredConnection.User, txtbCurrentConnectionsSearch.Text, RegexOptions.IgnoreCase) ||
                                                    Regex.IsMatch(filteredConnection.EnvType, txtbCurrentConnectionsSearch.Text, RegexOptions.IgnoreCase) ||
                                                    Regex.IsMatch(filteredConnection.Environment, txtbCurrentConnectionsSearch.Text, RegexOptions.IgnoreCase))
                                                   select filteredConnection;
            }
            catch (ArgumentException)
            {
                UpdateStatus("Invalid search criteria specified.", true);
            }
            catch (Exception ex)
            {
                UpdateStatus(ex.Message, true);
            }
        }

        /// <summary>
        /// Generates a set of connection display entities, which represent
        /// an aggregation of fields found within the various TibcoAdmin
        /// objects in the working data set. These display entities are used
        /// for binding to their respective data grids.
        /// </summary>
        /// <returns></returns>
        private IEnumerable<TibcoConnectionDisplayEntity> GenerateConnectionDisplayEntities()
        {
            Dictionary<string, TibcoConnectionDisplayEntity> consumersIndex = new Dictionary<string, TibcoConnectionDisplayEntity>();

            // Link each consumer with its associated connection by its
            // 'ConnectionID' and create display entities from the
            // combined query.
            Dictionary<string, ClientHost> clientHosts = profileCtrl.ClientHosts;
            List<TibcoConnectionDisplayEntity> displayEntities = new List<TibcoConnectionDisplayEntity>(from consumer in _data.Consumers.Values
                                                                                                        join conn in _data.Connections.Values on consumer.ConnectionID equals conn.ConnectionID
                                                                                                        select new TibcoConnectionDisplayEntity
                                                                                                        {
                                                                                                            ConnectionID = consumer.ConnectionID,
                                                                                                            Environment = clientHosts.ContainsKey(conn.Host) ? clientHosts[conn.Host].FriendlyName : String.Empty,
                                                                                                            EnvType = clientHosts.ContainsKey(conn.Host) ? clientHosts[conn.Host].EnvironmentType : String.Empty,
                                                                                                            QueueName = consumer.Queue,
                                                                                                            User = consumer.User,
                                                                                                            Host = conn.Host,
                                                                                                            Uptime = conn.Uptime,
                                                                                                            Active = consumer.Active,
                                                                                                            SSLEnabled = conn.SSLEnabled,
                                                                                                            ClientType = conn.ClientType
                                                                                                        });

            return displayEntities;
        }

        /// <summary>
        /// Updates the expander header label when the state of the expander
        /// has changed.
        /// </summary>
        private void UpdateExpanderHeader()
        {
            const string ProfileHeader = "Profile";
            const string None_String = "None";

            // Change Background Color and Header title change
            if (expProfiles.IsExpanded)
            {
                expProfiles.Header = String.Concat(ProfileHeader, "s");
            }
            else
            {
                string profileName = null;
                if (profileCtrl.SelectedItem is EmsServerProfile)
                {
                    profileName = (profileCtrl.SelectedItem as EmsServerProfile).ProfileDisplayName;
                }

                // If there is no profile name specified, check if there is a
                // profile that can be selected, otherwise show "None" in the
                // header.
                if (string.IsNullOrWhiteSpace(profileName))
                {
                    if (profileCtrl.EmsServerProfiles.Count > 0)
                    {
                        profileCtrl.SelectedIndex = 0;
                        profileName = (profileCtrl.SelectedItem as EmsServerProfile).ProfileDisplayName;
                    }
                    else
                    {
                        profileName = None_String;
                    }
                }

                expProfiles.Header = String.Concat(ProfileHeader, ": ", profileName);
            }
        }

        private void BuildBrushes()
        {
            _brushes = new Dictionary<string, LinearGradientBrush>();

            GradientStopCollection stopCol = new GradientStopCollection();
            stopCol.Add(new GradientStop(Colors.MistyRose, 0));
            stopCol.Add(new GradientStop(Colors.SeaShell, 0.5));
            stopCol.Add(new GradientStop(Colors.MistyRose, 1.0));

            _brushes.Add(BrushIllegalWarning, new LinearGradientBrush(stopCol, 90));

            stopCol = new GradientStopCollection();
            stopCol.Add(new GradientStop(Colors.Khaki, 0));
            stopCol.Add(new GradientStop(Colors.LightYellow, 0.5));
            stopCol.Add(new GradientStop(Colors.Khaki, 1.0));

            _brushes.Add(BrushWarning, new LinearGradientBrush(stopCol, 90));

            stopCol = new GradientStopCollection();
            stopCol.Add(new GradientStop(Colors.Silver, 0));
            stopCol.Add(new GradientStop(Colors.WhiteSmoke, 0.5));
            stopCol.Add(new GradientStop(Colors.Silver, 1.0));

            _brushes.Add(BrushProfileBar, new LinearGradientBrush(stopCol, 90));
        }

        private IOfflineDataGenerator CreateOfflineData()
        {
            IOfflineDataGenerator generator = null;

            try
            {
                Assembly generatorAsm = Assembly.Load(TibcoAdminConfigurationManager.GetOfflineDataAssemblyName());
                generator = generatorAsm.CreateInstance(TibcoAdminConfigurationManager.GetOfflineDataTypeName()) as IOfflineDataGenerator;  
            }
            catch (Exception ex)
            {
                UpdateStatus(ex.Message, true);
            }

            return generator;
        }

        #endregion //Private Methods
    }
}
